from google.appengine.ext import db
import re
import urllib2
import BeautifulSoup
import time
import random
import logging
import datetime

class Paste(db.Model):
    name = db.StringProperty(required=True)
    parent_paste = db.SelfReferenceProperty(collection_name="children")
    admin_only = db.BooleanProperty(required=True, default=False)
    mimetype = db.StringProperty(required=True)
    code = db.TextProperty()
    open = db.BooleanProperty(required=True, default=True)
    last_updated = db.DateTimeProperty(auto_now=True)


    def get_path(self):
        if not self.parent_paste:
            return "/" + self.name
        else:
            return self.parent_paste.get_path() + "/" + self.name

    def get_handlers(self):
        handlers = {}
        text = str(Paste.fetch("handlers").code)
        for line in text.splitlines():
            handler_paste_id, value, url = line.split("|")
            if handler_paste_id == self.name:
                handlers[value] = url
        return handlers
     
    @staticmethod
    def fetch(name):
        path = name.strip('/').split('/')
        paste = None
        for component in path:
            query = Paste.all()
            query.filter("name =", component)
            query.filter("parent_paste =", paste)
            paste = query.get()
            if not paste:
                return None
        return paste
    
    @staticmethod
    def update(name, mimetype, code, admin_only=False):
        paste = Paste.fetch(name)
        if not paste:
            paste = Paste(name=name, mimetype=mimetype)
        else:
            paste.mimetype=mimetype
        paste.code = code
        paste.admin_only = admin_only
        paste.put()
        return paste
    

class Country(db.Model):
    iso_a2 = db.StringProperty(required=True)
    iso_a3 = db.StringProperty(required=True)
    iso_code = db.IntegerProperty()
    name = db.StringProperty(required=True)
    aliases = db.StringListProperty()
    
    travel_warning = db.StringProperty()
    travel_alert = db.StringProperty()
    
    def add_alias(self, value):
        if self.aliases == None: self.aliases = []
        value = self.normalize_alias(value)
        if not value in self.aliases:
            self.aliases.append(value)
    
    def clear_vaccine_recommendations(self):
        for recommendation in self.vaccinerecommendation_set:
            recommendation.delete()
    
    @staticmethod
    def normalize_alias(value):
        value = re.sub("\([^\)]*\)", "", value) # remove ()
        value = value.replace("&amp;", "&")
        value = re.sub(" +", " ", value.lower().strip()) # replace multiple spaces with one
        value = value.replace("-", " ")
        value = value.strip()
        return value
    
    @staticmethod
    def get_name(iso_a2):
        query = Country.all()
        query.filter("iso_a2 = ", iso_a2.upper())
        for result in query:
            return result.name
        return None
    
    @staticmethod
    def fetch(key):
        key = str(key)
        if len(key) == 2:
            query = Country.all()
            query.filter("iso_a2 =", key.upper())
            result = query.get()
            if result: return result
        if len(key) == 3:
            query = Country.all()
            query.filter("iso_a3 =", key.upper())
            result = query.get()
            if result: return result
        if re.match("[0-9]*$", key):
            query = Country.all()
            query.filter("iso_code =", int(key))
            result = query.get()
            if result: return result
        key = Country.normalize_alias(key)
        query = Country.all()
        query.filter("aliases = ", key)
        return query.get()
            
class VaccineRecommendation(db.Model):
    country = db.ReferenceProperty(Country, required=True)
    vaccine = db.StringProperty(required=True)
    text = db.TextProperty(required=True)

class Account(db.Model):
    number = db.IntegerProperty(required=True)
    name = db.StringProperty(required=True)
    balance = db.FloatProperty(required=True)
    updated = db.StringProperty(required=True)
    historical = db.BooleanProperty(required=True, default=False)
    modified = db.DateTimeProperty(auto_now=True)
    
    def get_last_archived_version(self):
        query = Account.all()
        query.filter("number =", self.number)
        query.filter("historical =", True)
        query.order("-modified")
    
    
    @staticmethod
    def fetch(number):
        query = Account.all()
        query.filter("number =", number)
        query.filter("historical =", False)
        return query.get()
    
    @staticmethod
    def update(number, name, balance, updated):
        account = Account.fetch(number)
        if not account:
            account = Account(
                              number=number,
                              name = name,
                              balance=balance,
                              historical=False,
                              updated=updated
                              )
        else:
            account.name = name
            account.balance = balance
            account.updated = updated
        account.put()
        return account

class ExchangeRate(db.Model):
    date = db.DateProperty(required=True)
    unit = db.StringProperty(required=True)
    units_per_usd = db.FloatProperty(required=True)

    @staticmethod
    def __normalize_date(date):
        if date < datetime.date(1995, 11, 16):
            date = datetime.date(1995, 11, 16)
        if date > datetime.date.today():
            date = datetime.date.today()
        return date
        
    @staticmethod
    def clear_date(date):
        date = ExchangeRate.__normalize_date(date)
        query = ExchangeRate.all()
        query.filter("date = ", date)
        for exchange_rate in query:
            exchange_rate.delete()
            
    @staticmethod
    def __load_date(date):
        ExchangeRate.clear_date(date)
        url = "http://www.xe.com/ict/?basecur=USD&historical=true&month={1}&day={2}&year={0}".format(date.year, date.month, date.day)
        opener = urllib2.build_opener()
        opener.addheaders = [('User-agent', 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.142 Safari/535.19')]
        html = opener.open(url).read()
        soup = BeautifulSoup.BeautifulSoup(html)
        rows = soup("tr")[4:-1]
        for row in rows:
            row.extract()
            unit, name, units_per_usd, usd_per_unit = [td.text for td in row("td")]
            units_per_usd = float(units_per_usd.replace(",", ""))
            exchange_rate = ExchangeRate(date=date, unit=unit, units_per_usd=units_per_usd)
            exchange_rate.put()

    @staticmethod
    def is_date_loaded(date):
        date = ExchangeRate.__normalize_date(date)
        query = ExchangeRate.all()
        query.filter("date = ", date)
        return (query.count() > 0)

    @staticmethod
    def fetch_date(date):
        date = ExchangeRate.__normalize_date(date)
        if not ExchangeRate.is_date_loaded(date):
            ExchangeRate.__load_date(date)
            logging.info("Loaded exchange rates for date: " + str(date))
        
        rates = {}
        query = ExchangeRate.all()
        query.filter("date = ", date)
        for exchange_rate in query:
            rates[exchange_rate.unit] = exchange_rate.units_per_usd
        return rates

    @staticmethod
    def compare(date_from, date_to):
        delay = ExchangeRate.is_date_loaded(date_from)
        rates_from = ExchangeRate.fetch_date(date_from)
        if delay:
            time.sleep(random.random() * 2 + 2)
        rates_to = ExchangeRate.fetch_date(date_to)
        
        comparison = {}
        for unit in rates_from.keys():
            if not unit in rates_to.keys(): continue
            rate_from = rates_from[unit]
            rate_to = rates_to[unit]
            comparison[unit] = (rate_from, rate_to)
        return comparison

class Point(db.Expando):
    location = db.GeoPtProperty("Location", required=True)
    source = db.StringProperty("Source", default="default", required=True)
    title = db.StringProperty("Title", required=True)
    source_id = db.StringProperty("Source ID")
    description = db.StringProperty("Description")
    icon = db.StringProperty("Icon URL")
    modified = db.DateTimeProperty("Last Modified", auto_now=True)

    def put(self):
        if self.source_id:
            if Point.fetch_by_source_id(self.source, self.source_id):
                raise Exception("Duplicate source_id of '{}' for source '{}'.".format(self.source_id, self.source))
        db.Expando.__put__(self)

    @staticmethod
    def fetch_all(self, source=None):
        query = Point.all()
        if source:
            query.filter("source = ", source)
        return query

    @staticmethod
    def delete_all(self, source):
        query = Point.all()
        query.filter("source = ", source)
        for point in query:
            point.delete()

    @staticmethod
    def fetch_by_source_id(self, source, source_id):
        query = Point.all()
        query.filter("source = ", source)
        query.filter("source = ", source_id)
        return query.get()



















